﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using BrightIdeasSoftware;
using ROrganizer.Client;
using ROrganizer.UI.Controllers.Core;
using ROrganizer.UI.Models;

namespace ROrganizer.UI.Controllers
{
    internal class ROrganizerFormController : IModelFilter
    {
        private readonly FastObservableCollection<Entry> entries;
        private readonly FastObservableCollection<string> genres;
        private readonly List<string> chosen;
        private List<ImdbEntry> imdbs;
        private List<MetaEntry> metas;
        private GroupMode? grouping;

        public ROrganizerFormController()
        {
            this.entries = new FastObservableCollection<Entry>();
            this.genres = new FastObservableCollection<string>();
            this.chosen = new List<string>();
            this.Filter = String.Empty;

            this.Initialize(rechoose: true);
            this.Initialize(null);
        }

        private void Initialize(bool rechoose = false)
        {
            var irepository = new ImdbRepository();
            var mrepository = new MetaRepository();

            this.imdbs = irepository.All().ToList();
            this.metas = mrepository.All().ToList();

            this.TotalMovies = this.imdbs.Count;
            this.TotalSize = this.metas.Sum(x => x.Info.Size);
            this.TotalRuntime = this.metas.Where(x => this.imdbs.Any(i => i.File == x.File.Path)).Sum(x => x.Info.Runtime);

            if (rechoose)
            {
                this.chosen.Clear();
                this.chosen.AddRange(this.imdbs.SelectMany(x => x.Info.Genres).Distinct());
            }
            else
            {
                this.chosen.AddRange(this.imdbs.SelectMany(x => x.Info.Genres).Distinct().Except(this.genres));
            }

            this.genres.Clear();
            this.genres.AddRange(this.imdbs.SelectMany(x => x.Info.Genres).Distinct().OrderBy(x => x));
        }

        private void Initialize(GroupMode? mode)
        {
            this.grouping = mode;
            this.entries.Clear();

            var query = from ientry in this.imdbs
                        join mentry in this.metas on ientry.File equals mentry.File.Path into tmetas
                        from tmentry in tmetas.DefaultIfEmpty()
                        group new { Imdb = ientry, Meta = tmentry} by ientry.Info.Id into releases
                        let first = releases.First()
                        select new
                        {
                            Title = first.Imdb.Info.Title,
                            Rating = first.Imdb.Info.Rating,
                            Published = first.Imdb.Info.Published,
                            Genres = first.Imdb.Info.Genres,
                            Keywords = first.Imdb.Info.Keywords,
                            Tagline = first.Imdb.Info.Tagline,
                            Plot = first.Imdb.Info.Plot,
                            Resolution = Resolution.From(first.Meta),
                            Size = releases.Where(x => x.Meta != null).Sum(x => x.Meta.Info.Size),
                            Runtime = releases.Where(x => x.Meta != null).Sum(x => x.Meta.Info.Runtime),
                            Disks = releases.Count(),
                            Releases = releases.Select(x => x.Imdb).ToArray()
                        };

            if (this.grouping == ROrganizerFormController.GroupMode.ByGenre)
            {
                query = from ientry in this.imdbs
                        join mentry in this.metas on ientry.File equals mentry.File.Path into tmetas
                        from tmentry in tmetas.DefaultIfEmpty()
                        from genre in ientry.Info.Genres
                        group new { Entry = ientry, Genre = genre, Meta = tmentry } by new { ientry.Info.Id, genre } into releases
                        let first = releases.First()
                        select new
                        {
                            Title = first.Entry.Info.Title,
                            Rating = first.Entry.Info.Rating,
                            Published = first.Entry.Info.Published,
                            Genres = new[] { first.Genre },
                            Keywords = first.Entry.Info.Keywords,
                            Tagline = first.Entry.Info.Tagline,
                            Plot = first.Entry.Info.Plot,
                            Resolution = Resolution.From(first.Meta),
                            Size = releases.Where(x => x.Meta != null).Sum(x => x.Meta.Info.Size),
                            Runtime = releases.Where(x => x.Meta != null).Sum(x => x.Meta.Info.Runtime),
                            Disks = releases.Count(),
                            Releases = releases.Select(x => x.Entry).Distinct().ToArray()
                        };
            }

            if (this.grouping == ROrganizerFormController.GroupMode.ByKeword)
            {
                query = from ientry in this.imdbs
                        join mentry in this.metas on ientry.File equals mentry.File.Path into tmetas
                        from tmentry in tmetas.DefaultIfEmpty()
                        from keyword in ientry.Info.Keywords
                        group new { Entry = ientry, Keyword = keyword, Meta = tmentry } by new { ientry.Info.Id, Letter = keyword[0] } into releases
                        let first = releases.First()
                        select new
                        {
                            Title = first.Entry.Info.Title,
                            Rating = first.Entry.Info.Rating,
                            Published = first.Entry.Info.Published,
                            Genres = first.Entry.Info.Genres,
                            Keywords = releases.Select(x => x.Keyword).ToArray(),
                            Tagline = first.Entry.Info.Tagline,
                            Plot = first.Entry.Info.Plot,
                            Resolution = Resolution.From(first.Meta),
                            Size = releases.Where(x => x.Meta != null).Sum(x => x.Meta.Info.Size),
                            Runtime = releases.Where(x => x.Meta != null).Sum(x => x.Meta.Info.Runtime),
                            Disks = releases.Count(),
                            Releases = releases.Select(x => x.Entry).Distinct().ToArray()
                        };
            }

            var formatter = from entry in query
                            select new Entry
                            {
                                Title = entry.Title,
                                Rating = entry.Rating,
                                Published = entry.Published,
                                Genres = String.Join(", ", entry.Genres),
                                Keywords = String.Join(", ", entry.Keywords),
                                Resolution = entry.Resolution,
                                Size = entry.Size,
                                Runtime = entry.Runtime,
                                Disks = entry.Disks,
                                Targets = entry.Releases
                            };

            this.entries.AddRange(formatter);
        }

        public string Filter { get; set; }
        public void GroupBy(GroupMode? grouping)
        {
            this.Initialize(grouping);
        }

        public void SetGenreFilter(string genre, bool enabled)
        {
            if (!enabled)
                this.chosen.Remove(genre);

            if (enabled && !this.chosen.Contains(genre))
                this.chosen.Add(genre);
        }

        public bool IsGenreChoosen(string genre)
        {
            return this.chosen.Contains(genre);
        }

        public int TotalMovies { get; private set; }
        public long TotalSize { get; private set; }
        public int TotalRuntime { get; private set; }

        public ObservableCollection<Entry> Entries
        {
            get { return this.entries; }
        }

        public ObservableCollection<string> Genres
        {
            get { return this.genres; }
        }

        public void ShowLibraryManager(IWin32Window owner)
        {
            if (ViewManager.ShowLibraryManager(owner))
            {
                this.Initialize();
                this.Initialize(this.grouping);
            }
        }

        public void ShowProposalsManager(IWin32Window owner)
        {
            ViewManager.ShowProposalManager(owner);
        }

        public bool CanShowDetails()
        {
            return true;
        }

        public void ShowDetails(IWin32Window owner, Entry entry)
        {
            ViewManager.ShowItemDetails(owner, entry.Targets);
        }

        public bool CanCopyTo(Entry[] entries)
        {
            return entries.Any();
        }

        public void CopyTo(IWin32Window owner, Entry[] entries)
        {
            var query = from entry in entries
                        from target in entry.Targets
                        select target.File;

            var tasks = ViewManager.ShowCopySettings(owner, query.Distinct().ToArray());
            if (tasks == null || tasks.Length == 0)
                return;

            ViewManager.ShowProgress(owner, tasks);
        }

        public bool CanRemove(Entry[] entries)
        {
            return entries.Any();
        }

        public void Remove(IWin32Window owner, Entry[] entries)
        {
            var query = from entry in entries
                        from target in entry.Targets
                        select target.File;

            var tasks = ViewManager.ShowRemoveSettings(owner, query.Distinct().ToArray());
            if (tasks == null || tasks.Length == 0)
                return;

            try
            {
                ViewManager.ShowProgress(owner, tasks);
                this.entries.RemoveRange(entries);
            }
            catch
            {
                this.Initialize(rechoose: true);
                this.Initialize(this.grouping);
            }
        }

        public void ShowUpdate(IWin32Window owner)
        {
            if (ViewManager.ShowUpdate(owner))
            {
                this.Initialize();
                this.Initialize(this.grouping);
            }
        }

        public void ShowAbout(IWin32Window owner)
        {
            ViewManager.ShowAbout(owner);
        }

        public class Entry
        {
            public string Title { get; set; }
            public short? Rating { get; set; }
            public DateTime? Published { get; set; }
            public string Genres { get; set; }
            public string Keywords { get; set; }
            public string Tagline { get; set; }
            public string Plot { get; set; }
            public long Size { get; set; }
            public int Runtime { get; set; }
            public int Disks { get; set; }
            public Resolution Resolution { get; set; }

            public ImdbEntry[] Targets { get; set; }
        }

        public enum GroupMode
        {
            ByTitle,
            ByRating,
            ByPublicationDate,
            ByGenre,
            ByKeword,
            ByQuality,
            BySize
        }

        bool IModelFilter.Filter(object modelObject)
        {
            var entry = modelObject as Entry;
            var query = from target in entry.Targets
                        from genre in target.Info.Genres
                        join selected in this.chosen on genre equals selected
                        select genre;

            if (!query.Any())
                return false;

            if (String.IsNullOrEmpty(this.Filter))
                return true;

            return this.Filter.ToLower().Split(new[]{' '}, StringSplitOptions.RemoveEmptyEntries).All(filter =>
                   entry.Title.ToLower().Contains(filter)
                || entry.Tagline != null && entry.Tagline.ToLower().Contains(filter)
                || entry.Plot != null && entry.Plot.ToLower().Contains(filter)
                || entry.Keywords.ToLower().Contains(filter)
                || entry.Genres.ToLower().Contains(filter));
        }
    }
}
